local util    = require("shared.api.util")
-- // NOTE: must fix luajit-msgpack-pure type_code don't mismatch spec https://github.com/msgpack/msgpack/blob/master/spec.md 
local msgpack = require("resty.luajit-msgpack-pure")
local cjson   = require("cjson.safe")
      cjson.encode_max_depth(32)
      cjson.decode_max_depth(32)

local NULL = ngx.null

local _M = {}

local SCRIPTS = {
  Casino = {
    PeekTransfer = "gaas.Casino.PeekTransfer",
  },

  Fish = {
    -- Bet
    PeekBet             = "gaas.Fish.PeekBet",
    PlaceBet            = "gaas.Fish.PlaceBet",
    SettleBet           = "gaas.Fish.SettleBet",
    CancelBet           = "gaas.Fish.CancelBet",

    -- Bonus
    SettleBonus         = "gaas.Fish.SettleBonus",

    -- Stage
    InitStage           = "gaas.Fish.InitStage",
    DeinitStage         = "gaas.Fish.DeinitStage",
    PeekStage           = "gaas.Fish.PeekStage",

    -- SessionState
    InitSessionState    = "gaas.Fish.InitSessionState",
    KeepSessionState    = "gaas.Fish.KeepSessionState",
    PeekSessionState    = "gaas.Fish.PeekSessionState",

    -- Transfer
    CommitTransfer      = "gaas.Fish.CommitTransfer",
    PlaceTransfer       = "gaas.Fish.PlaceTransfer",
    FulfillTransfer     = "gaas.Fish.FulfillTransfer",
    CancelTransfer      = "gaas.Fish.CancelTransfer",
    AcceptTransfer      = "gaas.Fish.AcceptTransfer",
    RejectTransfer      = "gaas.Fish.RejectTransfer",
    ReviewTransfer      = "gaas.Fish.ReviewTransfer",
  },

  Player = {
    -- Wallet
    ListWallet        = "gaas.Player.ListWallet",
    PeekWallet        = "gaas.Player.PeekWallet",

    -- Transfer
    CommitTransfer    = "gaas.Player.CommitTransfer",
    LookupTransfer    = "gaas.Player.LookupTransfer",

    -- Permission
    GrantPermission   = "gaas.Player.GrantPermission",
    ListPermission    = "gaas.Player.ListPermission",
    RevokePermission  = "gaas.Player.RevokePermission",
  },
}

do
  local _M_mt = { __index = _M }

  -- local FORMATTER = "MSGPACK"
  local FORMATTER = "JSON"


  local function _get_script_key(workspace)
    return "db:"..workspace.."/scripts"
  end


  local function _decode_reply(content)
    if content then
      -- local _, datagram = msgpack.unpack(content)
      -- return datagram
      return cjson.decode(content)
    end
    return content
  end


  local function _exec_script(self, script_name, keys, argv)
    assert(self)
    assert(self.redis)
    assert(self.workspace)
    assert(script_name)

    local redis      = self.redis
    local script_key = _get_script_key(self.workspace)
    local script_db  = self.db  or  0
    local script_id

    do
      local _, err = redis:SELECT(script_db)
      if err then
        return nil, err
      end
      local reply, err = redis:HGET(script_key, script_name)
      if err then
        return nil, err
      end

      -- export
      script_id = reply
    end

    if not script_id  or  script_id==NULL  or  script_id=="" then
      return nil, string.format("missing script '%s'", script_name)
    end

    keys = keys or {}

    local cmdargs = util.merge_table(keys, argv) or {}

    return redis:EVALSHA(script_id, #keys, unpack(cmdargs))
  end


  function _M.create_repository(redis, workspace, db)
    assert(redis)
    assert(workspace)

    return setmetatable({
      redis       = redis,
      workspace   = workspace,
      db          = db  or 0,
    }, _M_mt)
  end


  -- Casino
  do
    local CASINO = {}

    -- Transfer
    do
      function CASINO.PeekTransfer(repo,
          provider,
          transaction_id)
        local reply, err = _exec_script(repo,
          SCRIPTS.Casino.PeekTransfer,
          {
            repo.workspace,
            provider,
            transaction_id,
          },
          util.normalize_field_value_args({
            FORMATTER = FORMATTER,
          })
        )
        return _decode_reply(reply), err
      end
    end

    -- export
    _M.Casino = CASINO
  end


  -- Fish
  do
    local FISH = {}

    -- Bet
    do
      function FISH.PeekBet(repo,
          ticket_id)
        local reply, err = _exec_script(repo,
          SCRIPTS.Fish.PeekBet,
          {
            repo.workspace,
            ticket_id,
          },
          util.normalize_field_value_args({
            FORMATTER = FORMATTER,
          })
        )
        return _decode_reply(reply), err
      end


      function FISH.PlaceBet(repo,
          server,
          session,
          game,
          room,
          round,
          provider,
          player,
          wallet,
          wager,
          stage,
          stage_type,
          account,
          account_type,
          dealer,
          dealer_player,
          casino,
          country,
          timezone,
          player_device,
          player_os,
          player_ip,
          operation_urn,
          timestamp)
        local reply, err = _exec_script(repo,
          SCRIPTS.Fish.PlaceBet,
          {
            repo.workspace,
            session,
            game,
            provider,
            player,
            wager,
            wallet,
            account,
            dealer,
            timezone,
            timestamp,
          },
          util.normalize_field_value_args({
            FORMATTER      = FORMATTER,
            OPERATION_URN  = operation_urn,
            SERVER         = server,
            ROUND          = round,
            STAGE          = stage,
            STAGE_TYPE     = stage_type,
            ROOM           = room,
            DEVICE         = player_device,
            OS_TYPE        = player_os,
            IP_ADDR        = player_ip,
            CASINO         = casino,
            DEALER_PLAYER  = dealer_player,
            ACCOUNT_TYPE   = account_type,
            COUNTRY        = country,
          })
        )
        return _decode_reply(reply), err
      end


      function FISH.SettleBet(repo,
          server,
          session,
          game,
          transaction_id,
          winlost,
          win_type,
          bonus,
          symbol,
          stage,
          stage_type,
          triggered_stage,
          triggered_stage_type,
          operation_urn,
          timestamp)
        local reply, err = _exec_script(repo,
          SCRIPTS.Fish.SettleBet,
          {
            repo.workspace,
            session,
            game,
            transaction_id,
            winlost,
            timestamp,
          },
          util.normalize_field_value_args({
            FORMATTER             = FORMATTER,
            OPERATION_URN         = operation_urn,
            SERVER                = server,
            WIN_TYPE              = win_type,
            BONUS                 = cjson.encode(bonus),
            SYMBOL                = cjson.encode(symbol),
            STAGE                 = stage,
            STAGE_TYPE            = stage_type,
            TRIGGERED_STAGE       = triggered_stage,
            TRIGGERED_STAGE_TYPE  = triggered_stage_type,
          })
        )
        return _decode_reply(reply), err
      end


      function FISH.CancelBet(repo,
          server,
          session,
          game,
          ticket_id,
          operation_urn,
          timestamp,
          forcible)
        local reply, err = _exec_script(repo,
          SCRIPTS.Fish.CancelBet,
          {
            repo.workspace,
            session,
            game,
            ticket_id,
            timestamp,
          },
          util.normalize_field_value_args({
            FORMATTER      = FORMATTER,
            OPERATION_URN  = operation_urn,
            SERVER         = server,
            FORCIBLE       = util.to_yes_no(forcible),
          })
        )
        return _decode_reply(reply), err
      end
    end


    -- Bonus
    do
      function FISH.SettleBonus(repo,
          server,
          session,
          game,
          room,
          provider,
          player,
          wallet,
          winlost,
          win_type,
          bonus,
          symbol,
          stage,
          stage_type,
          triggered_stage,
          triggered_stage_type,
          account,
          account_type,
          dealer,
          dealer_player,
          casino,
          country,
          timezone,
          player_device,
          player_os,
          player_ip,
          operation_urn,
          timestamp)
        local reply, err = _exec_script(repo,
          SCRIPTS.Fish.SettleBonus,
          {
            repo.workspace,
            session,
            game,
            provider,
            player,
            wallet,
            winlost,
            account,
            dealer,
            timezone,
            timestamp,
          },
          util.normalize_field_value_args({
            FORMATTER             = FORMATTER,
            OPERATION_URN         = operation_urn,
            SERVER                = server,
            STAGE                 = stage,
            STAGE_TYPE            = stage_type,
            TRIGGERED_STAGE       = triggered_stage,
            TRIGGERED_STAGE_TYPE  = triggered_stage_type,
            ROOM                  = room,
            DEVICE                = player_device,
            OS_TYPE               = player_os,
            IP_ADDR               = player_ip,
            WIN_TYPE              = win_type,
            BONUS                 = cjson.encode(bonus),
            SYMBOL                = cjson.encode(symbol),
            CASINO                = casino,
            DEALER_PLAYER         = dealer_player,
            ACCOUNT_TYPE          = account_type,
            COUNTRY               = country,
          })
        )
        return _decode_reply(reply), err
      end
    end


    -- Stage
    do
      function FISH.InitStage(repo,
          server,
          session,
          game,
          room,
          stage,
          stage_type,
          parent_stage,
          obtained_by,
          timestamp,
          forcible)
        local reply, err = _exec_script(repo,
          SCRIPTS.Fish.InitStage,
          {
            repo.workspace,
            session,
            game,
            stage,
            stage_type,
            parent_stage,
            timestamp,
          },
          util.normalize_field_value_args({
            FORMATTER    = FORMATTER,
            SERVER       = server,
            ROOM         = room,
            OBTAINED_BY  = obtained_by,
            FORCIBLE     = util.to_yes_no(forcible),
          })
        )
        return _decode_reply(reply), err
      end


      function FISH.DeinitStage(repo,
          server,
          session,
          game,
          room,
          stage,
          bet_count,
          timestamp)
        local reply, err = _exec_script(repo,
          SCRIPTS.Fish.DeinitStage,
          {
            repo.workspace,
            session,
            game,
            stage,
            timestamp,
          },
          util.normalize_field_value_args({
            FORMATTER  = FORMATTER,
            SERVER     = server,
            ROOM       = room,
            BET_COUNT  = bet_count,
          })
        )
        return _decode_reply(reply), err
      end


      function FISH.PeekStage(repo,
          server,
          session,
          game,
          room,
          stage)
        local reply, err = _exec_script(repo,
          SCRIPTS.Fish.PeekStage,
          {
            repo.workspace,
            session,
            game,
            stage,
          },
          util.normalize_field_value_args({
            FORMATTER  = FORMATTER,
            SERVER     = server,
            ROOM       = room,
          })
        )
        return _decode_reply(reply), err
      end
    end


    -- SessionState
    do
      function FISH.InitSessionState(repo,
          server,
          session,
          game,
          room,
          round,
          provider,
          player,
          wallet)
        local reply, err = _exec_script(repo,
          SCRIPTS.Fish.InitSessionState,
          {
            session,
            game,
            provider,
            player,
            wallet,
          },
          util.normalize_field_value_args({
            FORMATTER             = FORMATTER,
            SERVER                = server,
            ROOM                  = room,
            ROUND                 = round,
          })
        )
        return _decode_reply(reply), err
      end


      function FISH.KeepSessionState(repo,
          server,
          session,
          provider,
          player)
        local reply, err = _exec_script(repo,
          SCRIPTS.Fish.KeepSessionState,
          {
            session,
            provider,
            player,
          },
          util.normalize_field_value_args({
            FORMATTER             = FORMATTER,
            SERVER                = server,
          })
        )
        return _decode_reply(reply), err
      end


      function FISH.PeekSessionState(repo,
          server,
          session,
          provider,
          player)
        local reply, err = _exec_script(repo,
          SCRIPTS.Fish.PeekSessionState,
          {
            session,
            provider,
            player,
          },
          util.normalize_field_value_args({
            FORMATTER             = FORMATTER,
            SERVER                = server,
          })
        )
        return _decode_reply(reply), err
      end
    end


    -- Transfer
    do
      function FISH.CommitTransfer(repo,
          server,
          session,
          game,
          provider,
          player,
          wallet,
          currency,
          amount,
          dealer,
          dealer_player,
          casino,
          casino_transaction_id,
          operation_urn,
          timestamp,
          forcible,
          dismiss_wallet)
        local reply, err = _exec_script(repo,
          SCRIPTS.Fish.CommitTransfer,
          {
            repo.workspace,
            session,
            game,
            provider,
            player,
            wallet,
            currency,
            amount,
            operation_urn,
            timestamp,
          },
          util.normalize_field_value_args({
            FORMATTER              = FORMATTER,
            CASINO                 = casino,
            CASINO_TRANSACTION_ID  = casino_transaction_id,
            DEALER                 = dealer,
            DEALER_PLAYER          = dealer_player,
            SERVER                 = server,
            FORCIBLE               = util.to_yes_no(forcible),
            DISMISS_WALLET         = util.to_yes_no(dismiss_wallet),
          })
        )
        return _decode_reply(reply), err
      end


      function FISH.PlaceTransfer(repo,
          server,
          session,
          game,
          provider,
          player,
          wallet,
          currency,
          amount,
          dealer,
          dealer_player,
          casino,
          operation_urn,
          timestamp,
          forcible)
        local reply, err = _exec_script(repo,
          SCRIPTS.Fish.PlaceTransfer,
          {
            repo.workspace,
            session,
            game,
            provider,
            player,
            wallet,
            currency,
            amount,
            operation_urn,
            timestamp,
          },
          util.normalize_field_value_args({
            FORMATTER      = FORMATTER,
            CASINO         = casino,
            DEALER         = dealer,
            DEALER_PLAYER  = dealer_player,
            SERVER         = server,
            FORCIBLE       = util.to_yes_no(forcible),
          })
        )
        return _decode_reply(reply), err
      end


      function FISH.FulfillTransfer(repo,
          transfer_id,
          amount,
          casino_transaction_id,
          timestamp,
          forcible)
        local reply, err = _exec_script(repo,
          SCRIPTS.Fish.FulfillTransfer,
          {
            repo.workspace,
            transfer_id,
            timestamp,
          },
          util.normalize_field_value_args({
            FORMATTER              = FORMATTER,
            CASINO_TRANSACTION_ID  = casino_transaction_id,
            AMOUNT                 = amount,
            FORCIBLE               = util.to_yes_no(forcible),
          })
        )
        return _decode_reply(reply), err
      end


      function FISH.CancelTransfer(repo,
          transfer_id,
          casino_transaction_id,
          operation_urn,
          timestamp,
          forcible)
        local reply, err = _exec_script(repo,
          SCRIPTS.Fish.CancelTransfer,
          {
            repo.workspace,
            transfer_id,
            timestamp,
          },
          util.normalize_field_value_args({
            FORMATTER             = FORMATTER,
            CASINO_TRANSACTION_ID = casino_transaction_id,
            OPERATION_URN         = operation_urn,
            FORCIBLE              = util.to_yes_no(forcible),
          })
        )
        return _decode_reply(reply), err
      end


      function FISH.AcceptTransfer(repo,
          transfer_id,
          amount,
          casino_transaction_id,
          timestamp,
          forcible)
        local reply, err = _exec_script(repo,
          SCRIPTS.Fish.AcceptTransfer,
          {
            repo.workspace,
            transfer_id,
            timestamp,
          },
          util.normalize_field_value_args({
            FORMATTER             = FORMATTER,
            CASINO_TRANSACTION_ID = casino_transaction_id,
            AMOUNT                = amount,
            FORCIBLE              = forcible,
          })
        )
        return _decode_reply(reply), err
      end


      function FISH.RejectTransfer(repo,
          transfer_id,
          casino_transaction_id,
          operation_urn,
          timestamp,
          forcible)
        local reply, err = _exec_script(repo,
          SCRIPTS.Fish.RejectTransfer,
          {
            repo.workspace,
            transfer_id,
            timestamp,
          },
          util.normalize_field_value_args({
            FORMATTER             = FORMATTER,
            CASINO_TRANSACTION_ID = casino_transaction_id,
            OPERATION_URN         = operation_urn,
            FORCIBLE              = util.to_yes_no(forcible),
          })
        )
        return _decode_reply(reply), err
      end


      function FISH.ReviewTransfer(repo,
          transfer_id,
          timestamp)
        local reply, err = _exec_script(repo,
          SCRIPTS.Fish.ReviewTransfer,
          {
            repo.workspace,
            transfer_id,
            timestamp,
          },
          util.normalize_field_value_args({
            FORMATTER  = FORMATTER,
          })
        )
        return _decode_reply(reply), err
      end
    end

    -- export
    _M.Fish = FISH
  end


  -- Player
  do
    local PLAYER = {}


    -- Wallet
    do
      function PLAYER.ListWallet(repo,
          provider,
          player)
        local reply, err = _exec_script(repo,
          SCRIPTS.Player.ListWallet,
          {
            provider,
            player,
          },
          util.normalize_field_value_args({
            FORMATTER = FORMATTER,
          })
        )
        return _decode_reply(reply), err
      end


      function PLAYER.PeekWallet(repo,
          provider,
          player,
          wallet)
        local reply, err = _exec_script(repo,
          SCRIPTS.Player.PeekWallet,
          {
            provider,
            player,
            wallet,
          },
          util.normalize_field_value_args({
            FORMATTER = FORMATTER,
          })
        )
        return _decode_reply(reply), err
      end
    end


    -- Transfer
    do
      function PLAYER.CommitTransfer(repo,
          server,
          session,
          game,
          provider,
          player,
          wallet,
          currency,
          amount,
          dealer,
          dealer_player,
          casino,
          casino_transaction_id,
          operation_urn,
          timestamp,
          forcible)
        local reply, err = _exec_script(repo,
          SCRIPTS.Player.CommitTransfer,
          {
            repo.workspace,
            provider,
            player,
            wallet,
            currency,
            amount,
            operation_urn,
            timestamp,
          },
          util.normalize_field_value_args({
            FORMATTER             = FORMATTER,
            CASINO                = casino,
            CASINO_TRANSACTION_ID = casino_transaction_id,
            DEALER                = dealer,
            DEALER_PLAYER         = dealer_player,
            GAME                  = game,
            SERVER                = server,
            SESSION               = session,
            FORCIBLE              = util.to_yes_no(forcible),
          })
        )
        return _decode_reply(reply), err
      end


      function PLAYER.LookupTransfer(repo,
          provider,
          player,
          count,
          status,
          currency,
          wallet,
          transaction_id,
          transfer_id)
        local reply, err = _exec_script(repo,
          SCRIPTS.Player.LookupTransfer,
          {
            repo.workspace,
            provider,
            player,
          },
          util.normalize_field_value_args({
            FORMATTER       = FORMATTER,
            COUNT           = count,
            STATUS          = status,
            CURRENCY        = currency,
            WALLET          = wallet,
            TRANSACTION_ID  = transaction_id,
            TRANSFER_ID     = transfer_id,
          })
        )
        return _decode_reply(reply), err
      end
    end


    -- Permission
    do
      function PLAYER.GrantPermission(repo,
          provider,
          player,
          permission,
          privilege)
        local reply, err = _exec_script(repo,
          SCRIPTS.Player.GrantPermission,
          {
            provider,
            player,
            permission,
          },
          util.normalize_field_value_args({
            FORMATTER = FORMATTER,
            PRIVILEGE = privilege,
          })
        )
        return _decode_reply(reply), err
      end


      function PLAYER.ListPermission(repo,
          provider,
          player,
          privilege)
        local reply, err = _exec_script(repo,
          SCRIPTS.Player.ListPermission,
          {
            provider,
            player,
          },
          util.normalize_field_value_args({
            FORMATTER = FORMATTER,
            PRIVILEGE = privilege,
          })
        )
        return _decode_reply(reply), err
      end


      function PLAYER.RevokePermission(repo,
          provider,
          player,
          permission)
        local reply, err = _exec_script(repo,
          SCRIPTS.Player.RevokePermission,
          {
            provider,
            player,
            permission,
          },
          util.normalize_field_value_args({
            FORMATTER = FORMATTER,
          })
        )
        return _decode_reply(reply), err
      end
    end

    -- export
    _M.Player = PLAYER
  end

end
return _M
